import sys, os, copy, math
from topology_engineer import *
from robust_multi_traffic_topology_engineer import *
sys.path.append("..")
#import aurora_network
from gurobipy import *

'''		
Design fractional topology based on an expected traffic matrix, which is obtained as a component-wise max
of historical traffic matrices
'''
class HistoricalMaxTrafficTopologyEngineer(RobustMultiTrafficTopologyEngineer):
	def __init__(self, aurora_network, reconfig_length, training_length, all_paths, all_traffic_snapshots):
		num_snapshots = len(all_traffic_snapshots)
		RobustMultiTrafficTopologyEngineer.__init__(self, aurora_network, training_length, reconfig_length, all_paths, all_traffic_snapshots, 1)
		self.toe_class = TopologyEngineeringClass.AVE_TOE
		## NOTE: have to rezero the cached logical topologies here because when we initialize the parent class,
		## 		it will assume that the topology is static (see TopologyEngineer class above).
		self.cached_logical_topologies = {}
		return

	def cache_logical_topologies(self, round_to_integer=False):
		self.cached_logical_topologies[(0, self.training_length - 1)] = self._generate_uniform_logical_topology()
		## Start computing the logical topologies
		current_time = self.training_length
		print("Caching logical topologies for MAX ToE.....")
		while current_time < self.num_snapshots:
			ending_time = min(current_time + self.reconfig_length - 1, self.num_snapshots - 1)
			training_snapshots = self.all_traffic_snapshots[current_time - self.training_length : current_time]
			adj_matrix = self._topology_engineer(training_snapshots, self.all_paths)
			if round_to_integer:
				adj_matrix = self.aurora_network.round_fractional_topology_giant_switch(adj_matrix, [])
			self.cached_logical_topologies[(current_time, ending_time)] = adj_matrix
			current_time += self.reconfig_length
		print("Caching logical topologies: COMPLETE \n")
		return

	def get_filename_param(self):
		return "maxtoe_r{}t{}".format(self.reconfig_length, self.training_length)

	# returns a string description of this class
	def get_string(self):
		return "MAX traffic ToE"

	# training_traffic_snapshots - all the snapshots used for training the topology
	def _topology_engineer(self, training_traffic_snapshots, all_paths):
		nblocks = self.aurora_network.get_num_blocks()
		adj_matrix = np.zeros((nblocks, nblocks))
		traffic_matrices = np.array([self.aurora_network.get_traffic_matrix_from_traffic_snapshot(x) for x in training_traffic_snapshots])
		num_training_snapshots = len(traffic_matrices)
		expected_tm = np.zeros((nblocks, nblocks,))
		for i in range(nblocks):
			for j in range(nblocks):
				if i != j:
					for tm_index in range(num_training_snapshots):
						expected_tm[i][j] = max(traffic_matrices[tm_index][i][j], expected_tm[i][j])
		mu = self._maximize_traffic_scaleup(expected_tm, all_paths)
		adj_matrix = None
		relaxation = 1.
		while adj_matrix is None:
			adj_matrix = self._minimize_multihop(expected_tm, relaxation * mu, all_paths)
			relaxation -= 0.01
		#adj_matrix = self._reinforce_fractional_topology(adj_matrix)
		return adj_matrix

	# training_traffic_snapshots - all the snapshots used for training the topology
	def topology_engineer_given_TMs(self, traffic_matrices, all_paths):
		nblocks = self.aurora_network.get_num_blocks()
		adj_matrix = np.zeros((nblocks, nblocks))
		num_training_snapshots = len(traffic_matrices)
		expected_tm = np.zeros((nblocks, nblocks,))
		for i in range(nblocks):
			for j in range(nblocks):
				if i != j:
					for tm_index in range(num_training_snapshots):
						expected_tm[i][j] = max(traffic_matrices[tm_index][i][j], expected_tm[i][j])
		mu = self._maximize_traffic_scaleup(expected_tm, all_paths)
		adj_matrix = None
		relaxation = 1.
		while adj_matrix is None:
			adj_matrix = self._minimize_multihop(expected_tm, relaxation * mu, all_paths)
			relaxation -= 0.01
		#adj_matrix = self._reinforce_fractional_topology(adj_matrix)
		return adj_matrix